// CoreCalc, a spreadsheet core implementation 

// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyright
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using CoreCalc.SheetDefinedFunctions;
using System.Diagnostics;
using System.Reflection;
using C5;

// Machinery to define built-in functions and operators

namespace CoreCalc {
  class Function {
    public readonly String name;
    public enum TargetPlatform { CPU, GPU }
    public static TargetPlatform target;

    private Applier applierCPU;
    private Applier applierGPU;
    
    public readonly int fixity; // If non-zero: precedence of operator
    private bool isPlaceHolder; // May be overwritten by an SDF later, in CreateFunction
    private static readonly System.Collections.Generic.IDictionary<String, Function> table;

    public static readonly Type type = typeof(Function);

    private static readonly Random random = new Random();
    private static readonly long basedate = new DateTime(1899, 12, 30).Ticks;
    private static readonly double daysPerTick = 100E-9 / 60 / 60 / 24;
    private static ParallelMath.ParMath parMath = ParallelMath.ParMath.Instance;

    public Applier Applier
    {
        get {
            return applierGPU == null || target == TargetPlatform.CPU ? applierCPU : applierGPU; }
      set
      {
        Debug.Assert(isPlaceHolder);
        applierCPU = value;
        isPlaceHolder = false;
      }
    }

      // The following methods are used also from compiled SDFs:
    public static double ExcelAtan2(double x, double y)
    {
      return Math.Atan2(y, x);      // Note swapped arguments
    }

    public static double ExcelCeiling(double d, double signif)
    {
      return signif * Math.Ceiling(d / signif);
    }

    public static Value ExcelConcat(Value v0, Value v1)
    {
      if ((v0 is TextValue || v0 is NumberValue) && (v1 is TextValue || v1 is NumberValue))
      {
        String s0 = v0.ToString(), s1 = v1.ToString();
        // Avoid creating new String or TextValue objects when possible
        if (s0 == "")
          return v1 as TextValue ?? new TextValue(s1);
        else if (s1 == "")
          return v0 as TextValue ?? new TextValue(s0);
        else
          return new TextValue(s0 + s1);
      }
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static double ExcelFloor(double d, double signif)
    {
      return signif * Math.Floor(d / signif);
    }

    public static double ExcelMod(double x, double y)
    {
      return x - y * Math.Floor(x / y);
    }

    public static double ExcelNow()
    {
      return (DateTime.Now.Ticks - basedate) * daysPerTick;
    }


    public static double ExcelPow(double x, double y)
    { // Necessary because MS .NET and Mono Math.Pow is wrong
      if (double.IsNaN(x))
        return x;
      else if (double.IsNaN(y))
        return y;
      else
        return Math.Pow(x, y);
    }
    
    public static double ExcelRand()
    {
      return random.NextDouble();
    }

    public static double ExcelRound(double d, double digits)
    {
      int idigits = (int)digits; // Truncation towards zero is correct
      if (idigits >= 0)
        return Math.Round(d, idigits, MidpointRounding.AwayFromZero);
      else
      {
        double scale = Math.Pow(10, -idigits);
        return scale * Math.Round(d / scale, 0);
      }
    }

    public static double Average(Value[] vs)
    {
      // May consider whether empty cells and texts should just
      // be ignored instead of given ArgTypeError.
      // Use Kahan's accurate sum algorithm; see Goldberg 1991.
      double S = 0.0, C = 0.0;
      int count = 0;
      foreach (Value outerV in vs)
        outerV.Apply(delegate(Value v)
        {
          double Y = NumberValue.ToDoubleOrNan(v) - C, T = S + Y;
          C = (T - S) - Y;
          S = T;
          count++;
        });
      return S / count;
    }

    public static Value CArray(Value v0, Value v1, Value v2)
    {
      if (v1 is NumberValue && v2 is NumberValue)
      {
        int rows = (int)(v1 as NumberValue).value,
          cols = (int)(v2 as NumberValue).value;
        if (0 <= rows && 0 <= cols)
        {
          Value[,] result = new Value[cols, rows];
          for (int c = 0; c < cols; c++)
            for (int r = 0; r < rows; r++)
              result[c, r] = v0;
          return new ArrayExplicit(result);
        }
        else
          return ErrorValue.Make("#SIZE");
      }
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static Value ColMap(Value v0, Value v1)
    {
      if (v0 is ArrayValue && v1 is FunctionValue)
      {
        ArrayValue arr = v0 as ArrayValue;
        FunctionValue fv = v1 as FunctionValue;
        if (fv.Arity != arr.Rows)
          return ErrorValue.argCountErrorValue;
        Value[,] result = new Value[arr.Cols, 1];
        Value[] arguments = new Value[arr.Rows];
        for (int c = 0; c < arr.Cols; c++)
        {
          for (int r = 0; r < arr.Rows; r++)
            arguments[r] = arr[c, r];
          result[c, 0] = fv.Apply(arguments);
        }
        return new ArrayExplicit(result);
      }
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static Value CountIf(Value v0, Value v1)
    {
      FunctionValue fun = v1 as FunctionValue;
      if (fun == null)
        return ErrorValue.argTypeErrorValue;
      if (fun.Arity != 1)
        return ErrorValue.argCountErrorValue;
      double count = 0.0;
      v0.Apply(delegate(Value v)
        {
          if (!Double.IsNaN(count))
          {
            double condition = NumberValue.ToDoubleOrNan(fun.Call1(v));
            if (Double.IsNaN(condition))
              count = condition; // Hack: Error propagation from predicate
            else if (condition != 0)
              count++;
          }
          });
      return NumberValue.Make(count);
    }

    public static Value HArray(Value[] vs)
    {
      Value[,] result = new Value[vs.Length, 1];
      for (int c = 0; c < vs.Length; c++)
        result[c, 0] = vs[c];
      return new ArrayExplicit(result);
    }

    // Make array that is the horizontal (side-by-side) concatenation of arguments' columns
    public static Value HCat(Value[] vs)
    {
      int rows = 0, cols = 0;
      foreach (Value v in vs)
        if (v is ArrayValue)
        {
          rows = Math.Max(rows, (v as ArrayValue).Rows);
          cols += (v as ArrayValue).Cols;
        }
        else
        {
          rows = Math.Max(rows, 1);
          cols += 1;
        }
      foreach (Value v in vs)
        if (v is ArrayValue && (v as ArrayValue).Rows != rows)
          return ErrorValue.Make("Row counts differ");
      Value[,] result = new Value[cols, rows];
      int nextCol = 0;
      foreach (Value v in vs)
        if (v is ArrayValue)
        {
          ArrayValue arr = v as ArrayValue;
          for (int c = 0; c < arr.Cols; c++)
          {
            for (int r = 0; r < rows; r++)
              result[nextCol, r] = arr[c, r];
            nextCol++;
          }
        }
        else
        {
          for (int r = 0; r < rows; r++)
            result[nextCol, r] = v;
          nextCol++;
        }
      return new ArrayExplicit(result);
    }

    public static double IsArray(Value v0)
    {
      return v0 != null && v0 is ArrayValue ? 1.0 : 0.0;
    }

    public static double IsError(Value v0)
    {
      return v0 != null && v0 is ErrorValue ? 1.0 : 0.0;
    }

    public static Value Map(Value v0, Value v1)
    {
      FunctionValue fun = v1 as FunctionValue;
      if (fun == null)
        return ErrorValue.argTypeErrorValue;
      if (fun.Arity != 1)
        return ErrorValue.argCountErrorValue;
      ArrayValue v0arr = v0 as ArrayValue;
      if (v0arr == null)
        return ErrorValue.argTypeErrorValue;
      int cols = v0arr.Cols, rows = v0arr.Rows;
      Value[,] result = new Value[cols, rows];
      for (int c = 0; c < cols; c++)
        for (int r = 0; r < rows; r++)
          result[c, r] = fun.Call1(v0arr[c, r]);
      return new ArrayExplicit(result);
    }

     

    public static Value MapGPU(Value v0, Value v1)
    {
        FunctionValue fun = v1 as FunctionValue;
        if (fun == null)
            return ErrorValue.argTypeErrorValue;
        if (fun.Arity != 1)
            return ErrorValue.argCountErrorValue;
        ArrayValue v0arr = v0 as ArrayValue;
        if (v0arr == null)
            return ErrorValue.argTypeErrorValue;

         int cols = v0arr.Cols, rows = v0arr.Rows;
        int size = cols*rows;
       
       
        float[] values = new float[size];

        for (int i = 0; i < size; i++)
            values[i] = (float)NumberValue.ToDoubleOrNan(v0arr[i % cols, (int)Math.Floor((float)i / cols)]);

       ArrayValue result = fun.sdfInfo.GenerateAcceleratorMethod(cols,rows,values) as ArrayValue;
        
        return result;
    }

    public static double Max(Value[] vs)
    {
      double result = Double.NegativeInfinity;
      foreach (Value outerV in vs)
        outerV.Apply(delegate(Value v)
        {
          result = Math.Max(result, NumberValue.ToDoubleOrNan(v));
       });
      return result;
    }

    public static double Min(Value[] vs)
    {
      double result = Double.PositiveInfinity;
      foreach (Value outerV in vs)
        outerV.Apply(delegate(Value v)
        {
          result = Math.Min(result, NumberValue.ToDoubleOrNan(v));
        });
      return result;
    }

    public static Value Reduce(Value v0, Value v1, Value v2)
    {
      if (v0 is ArrayValue && v2 is FunctionValue)
      {
        ArrayValue arr = v0 as ArrayValue;
        FunctionValue fv = v2 as FunctionValue;
        if (fv.Arity != 2)
          return ErrorValue.argCountErrorValue;
        Value result = v1;
        for (int r = 0; r < arr.Rows; r++)
          for (int c = 0; c < arr.Cols; c++)
            result = fv.Call2(result, arr[c, r]);
        return result;
      }
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static Value RowMap(Value v0, Value v1)
    {
      if (v0 is ArrayValue && v1 is FunctionValue)
      {
        ArrayValue arr = v0 as ArrayValue;
        FunctionValue fv = v1 as FunctionValue;
        if (fv.Arity != arr.Cols)
          return ErrorValue.argCountErrorValue;
        Value[,] result = new Value[1, arr.Rows];
        Value[] arguments = new Value[arr.Cols];
        for (int r = 0; r < arr.Rows; r++)
        {
          for (int c = 0; c < arr.Cols; c++)
            arguments[c] = arr[c, r];
          result[0, r] = fv.Apply(arguments);
        }
        return new ArrayExplicit(result);
      }
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static Value Slice(Value v0, Value v1, Value v2, Value v3, Value v4)
    {    
      if (v0 is ArrayValue && v1 is NumberValue && v2 is NumberValue
                           && v3 is NumberValue && v4 is NumberValue)
      {
        double r1 = (v1 as NumberValue).value, c1 = (v2 as NumberValue).value,
               r2 = (v3 as NumberValue).value, c2 = (v4 as NumberValue).value;
        return (v0 as ArrayValue).Slice(r1, c1, r2, c2);
      }
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static double SumGPU(Value[] vs)
    {
        ArrayValue val = (ArrayValue)vs[0];
        int r = val.Rows;
        int c = val.Cols;

        float[,] sumValues = new float[r, c];
      
      for(int ri = 0;ri<r;ri++)
      {
           for(int ci = 0;ci<c;ci++)
          {
               sumValues[ri, ci] = (float)NumberValue.ToDoubleOrNan(val[ci,ri]);
          }
      }
  
         
      return (double)parMath.sum(sumValues);
    }

    public static double Sum(Value[] vs)
    {
        // May consider whether empty cells and texts should just
        // be ignored instead of giving ArgTypeError.
        // Use Kahan's accurate sum algorithm; see Goldberg 1991.
        double S = 0.0, C = 0.0;
        foreach (Value outerV in vs)
            outerV.Apply(delegate(Value v)
            {
                double Y = NumberValue.ToDoubleOrNan(v) - C, T = S + Y;
                C = (T - S) - Y;
                S = T;
            });
        return S;
    }


    public static ArrayValue Minverse(Value[] vs)
    {
        
       ArrayValue A = (ArrayValue)vs[0] ;
       
        
           float[,] a = new float[A.Cols, A.Rows];
           float[,] y;

             for (int r = 0; r < A.Rows; r++)
                 for (int c = 0; c < A.Cols; c++)
                     a[c, r] = (float)NumberValue.ToDoubleOrNan(A[r,c]);


             y = ParallelMath.NonParallelMath.inverse(a);

             Value[,] res = new Value[A.Cols, A.Rows];

             for (int r = 0; r < A.Rows; r++)
                 for (int c = 0; c < A.Cols; c++)
                 {
                     res[c, r] = NumberValue.FromDouble((double)y[r, c]);
                 }
             return new ArrayExplicit(res);
    }

    public static ArrayValue MMult(Value vs1, Value vs2)
    {
        ArrayValue A = (ArrayValue)vs1;
        ArrayValue B = (ArrayValue)vs2;
        int
          aRows = A.Rows,
          aCols = A.Cols,
          bRows = B.Rows,
          bCols = B.Cols,
          rRows = A.Rows,
          rCols = B.Cols;
         
          Value[,] result = new Value[rRows,rCols];

        if (aCols == bRows && rRows == aRows && rCols == bCols)
        {
            for (int r = 0; r < rRows; r++)
                for (int c = 0; c < rCols; c++)
                {
                    double sum = 0.0;
                    for (int k = 0; k < aCols; k++)
                    {
                        
                       sum += (NumberValue.ToDoubleOrNan(A[k, r]))*(NumberValue.ToDoubleOrNan(B[c, k]));
                    }
                    result[c, r] = NumberValue.Make( sum);
                }
        }
       
         
        return new ArrayExplicit(result);
   }

    public static Value MMultGPU(Value vs1, Value vs2)
     {
         ArrayValue A = (vs1 as ArrayValue);
         ArrayValue B = (vs2 as ArrayValue);
         int
           aRows = A.Rows,
           aCols = A.Cols,
           bRows = B.Rows,
           bCols = B.Cols,
           rRows = A.Rows,
           rCols = B.Cols;

         float[,] af = new float[rRows, rCols];
         float[,] bf = new float[rRows, rCols];

             for (int r = 0; r < rRows; r++)
                 for (int c = 0; c < rCols; c++)
                 {
                   

                         af[c, r] = (float)NumberValue.ToDoubleOrNan(A[r,c]);
                         bf[c, r] = (float)NumberValue.ToDoubleOrNan(B[r,c]);
                   
                    
                 }

         Value[,] res = new Value[rRows,rCols];
             float[,] result = parMath.mmul(af, bf);
              for (int r = 0; r < rRows; r++)
                  for (int c = 0; c < rCols; c++)
                  {
                      res[c, r] = NumberValue.FromDouble((double)result[r, c]);
                  }
         return new ArrayExplicit(res);
     }
   

    public static Value SumIf(Value v0, Value v1)
    {
      FunctionValue fun = v1 as FunctionValue;
      if (fun == null)
        return ErrorValue.argTypeErrorValue;
      if (fun.Arity != 1)
        return ErrorValue.argCountErrorValue;
      // Use Kahan's accurate sum algorithm; see Goldberg 1991.
      double S = 0.0, C = 0.0;
      v0.Apply(delegate(Value v)
        {
          if (!Double.IsNaN(S))
          {
            double condition = NumberValue.ToDoubleOrNan(fun.Call1(v));
            if (Double.IsNaN(condition))
              S = condition; // Error propagation from predicate
            else if (condition != 0)
            {
              double Y = NumberValue.ToDoubleOrNan(v) - C, T = S + Y;
              C = (T - S) - Y;
              S = T;
            }
          }
        });
      return NumberValue.Make(S);
    }
    public static Value TabulateSUM(Value v0, Value v1, Value v2)
    {
        if (v0 is FunctionValue && v1 is NumberValue && v2 is NumberValue)
        {
            FunctionValue fv = v0 as FunctionValue;
            if (fv.Arity != 2)
                return ErrorValue.argCountErrorValue;
            int rows = (int)(v1 as NumberValue).value,
              cols = (int)(v2 as NumberValue).value;
            if (0 <= rows && 0 <= cols)
            {
                double result = 0;
                for (int c = 0; c < cols; c++)
                    for (int r = 0; r < rows; r++)
                        result += (fv.Call2(NumberValue.Make(r + 1), NumberValue.Make(c + 1)) as NumberValue).value;
                return NumberValue.Make(result);
            }
            else
                return ErrorValue.Make("#SIZE");
        }
        else
            return ErrorValue.argTypeErrorValue;
    }

    public static Value Tabulate(Value v0, Value v1, Value v2)
    {
        //SdfManager.testWatch.Reset();
        

      if (v0 is FunctionValue && v1 is NumberValue && v2 is NumberValue)
      {
        FunctionValue fv = v0 as FunctionValue;
        if (fv.Arity != 2)
          return ErrorValue.argCountErrorValue;
        int rows = (int)(v1 as NumberValue).value,
          cols = (int)(v2 as NumberValue).value;
        if (0 <= rows && 0 <= cols)
        {
          Value[,] result = new Value[cols, rows];
          for (int c = 0; c < cols; c++)
            for (int r = 0; r < rows; r++)
              result[c, r] = fv.Call2(NumberValue.Make(r + 1), NumberValue.Make(c + 1));

            
          return new ArrayExplicit(result);
        }
        else
          return ErrorValue.Make("#SIZE");
      }
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static Value TabulateGPU(Value v0, Value v1, Value v2)
    {
        //SdfManager.testWatch.Reset();
        //SdfManager.testWatch.Start();
        if (v0 is FunctionValue && v1 is NumberValue && v2 is NumberValue)
        {
            FunctionValue fv = v0 as FunctionValue;
            if (fv.Arity != 2)
                return ErrorValue.argCountErrorValue;
            int rows = (int)(v1 as NumberValue).value,
              cols = (int)(v2 as NumberValue).value;
            if (0 <= rows && 0 <= cols)
            {
                int size = cols * rows;
                
                float[] values1 = new float[cols*rows];
                float[] values2 = new float[cols*rows];
                int c = 0;
                for (int i = 0; i < rows; i++)
                    for (int j = 0; j < cols; j++)
                    {
                        values1[c] = 1+i;
                        values2[c] = 1+j;
                        c++;
                    }




                ArrayValue result = fv.sdfInfo.GenerateAcceleratorMethod(cols, rows, values1, values2) as ArrayValue;


            

                //Console.WriteLine("Total: " + SdfManager.testWatch.Elapsed);
                return result;

            }
            else
                return ErrorValue.Make("#SIZE");
        }
        else
            return ErrorValue.argTypeErrorValue;
    }

   
    public static Value Transpose(Value v0)
    {
      ArrayValue v0arr = v0 as ArrayValue;
      if (v0arr != null)
      {
        int cols = v0arr.Rows, rows = v0arr.Cols;
        Value[,] result = new Value[cols, rows];
        for (int c = 0; c < cols; c++)
          for (int r = 0; r < rows; r++)
            result[c, r] = v0arr[r, c];
        return new ArrayExplicit(result);
      }
      else if (v0 is ErrorValue)
        return v0;
      else
        return ErrorValue.argTypeErrorValue;
    }

    public static Value VArray(Value[] vs)
    {
      Value[,] result = new Value[1, vs.Length];
      for (int r = 0; r < vs.Length; r++)
        result[0, r] = vs[r];
      return new ArrayExplicit(result);
    }

    // Make array as vertical concatenation (stack) of the arguments' rows
    public static Value VCat(Value[] vs)
    {
      int rows = 0, cols = 0;
      foreach (Value v in vs)
        if (v is ArrayValue)
        {
          cols = Math.Max(cols, (v as ArrayValue).Cols);
          rows += (v as ArrayValue).Rows;
        }
        else
        {
          cols = Math.Max(cols, 1);
          rows += 1;
        }
      foreach (Value v in vs)
        if (v is ArrayValue && (v as ArrayValue).Cols != cols)
          return ErrorValue.Make("Column counts differ");
      Value[,] result = new Value[cols, rows];
      int nextRow = 0;
      foreach (Value v in vs)
        if (v is ArrayValue)
        {
          ArrayValue arr = v as ArrayValue;
          for (int r = 0; r < arr.Rows; r++)
          {
            for (int c = 0; c < cols; c++)
              result[c, nextRow] = arr[c, r];
            nextRow++;
          }
        }
        else
        {
          for (int c = 0; c < cols; c++)            
            result[c, nextRow] = v;
          nextRow++;
        }
      return new ArrayExplicit(result);
    }

    // Get a Function by name
    public static Function Get(String name) {
      Function result;
      if (table.TryGetValue(name.ToUpper(), out result))
        return result;
      else
        return null;
    }

    public bool IsPlaceHolder { get { return isPlaceHolder; } }

    internal static bool Exists(string name)
    {
      return table.ContainsKey(name.ToUpper());
    }

    internal static void Remove(string methodName)
    {
      table.Remove(methodName);
    }

    static Function() {
      table = new Dictionary<String, Function>();
      // <fun> : unit -> number
      new Function("NOW",
                   MakeFunction(ExcelNow));
      new Function("PI",
                   MakeConstant(NumberValue.PI));
      new Function("RAND",
                   MakeFunction(ExcelRand));
      // <fun> : number -> number
      new Function("ABS",
          MakeFunction(Math.Abs));
      new Function("ASIN",
          MakeFunction(Math.Asin));
      new Function("ACOS",
          MakeFunction(Math.Acos));
      new Function("ATAN",
          MakeFunction(Math.Atan));
      new Function("COS",
          MakeFunction(Math.Cos));
      new Function("EXP",
          MakeFunction(Math.Exp));
      new Function("LN",
          MakeFunction((Fun<double, double>)Math.Log));
      new Function("LOG",
          MakeFunction(Math.Log10));
      new Function("LOG10",
          MakeFunction(Math.Log10));
      new Function("NEG", 9,        // pseudo-infix, to print neatly
          MakeFunction(delegate(double x) { return -x; }));
      new Function("SIN",
          MakeFunction(Math.Sin));
      new Function("SQRT",
          MakeFunction(Math.Sqrt));
      new Function("TAN",
          MakeFunction(Math.Tan));
      // <fun> : number * number -> number
      new Function("ATAN2",
          MakeFunction(ExcelAtan2));
      new Function("CEILING",
          MakeFunction(ExcelCeiling));
      new Function("FLOOR",
          MakeFunction(ExcelFloor));
      new Function("MOD",
          MakeFunction(ExcelMod));
      new Function("ROUND",
          MakeFunction(ExcelRound));
      new Function("^", 8,
          MakeFunction(ExcelPow));
      new Function("*", 7,
          MakeFunction(delegate(double x, double y) { return x * y; }));
      new Function("/", 7,
          MakeFunction(delegate(double x, double y) { return x / y; }));
      new Function("+", 6,
          MakeFunction(delegate(double x, double y) { return x + y; }));
      new Function("-", 6,
          MakeFunction(delegate(double x, double y) { return x - y; }));
      new Function("&", 6,
          MakeFunction(ExcelConcat));
      new Function("<", 5,
          MakePredicate(delegate(double x, double y) { return x < y; }));
      new Function("<=", 5,
          MakePredicate(delegate(double x, double y) { return x <= y; }));
      new Function(">=", 5,
          MakePredicate(delegate(double x, double y) { return x >= y; }));
      new Function(">", 5,
          MakePredicate(delegate(double x, double y) { return x > y; }));
      new Function("=", 4,
          MakePredicate(delegate(double x, double y) { return x == y; }));
      new Function("<>", 4,
          MakePredicate(delegate(double x, double y) { return x != y; }));
      // AND : number* -> number, 
      new Function("AND",                  // Note: non-strict in args 2, 3, ...
          delegate(Sheet sheet, Expr[] es, int col, int row)
          {
            for (int i = 0; i < es.Length; i++)
            {
              Value vi = es[i].Eval(sheet, col, row);
              NumberValue ni = vi as NumberValue;
              if (ni != null)
              {
                if (ni.value == 0)
                  return NumberValue.ZERO;
              }
              else if (vi is ErrorValue)
                return vi;
              else
                return ErrorValue.argTypeErrorValue;
            }
            return NumberValue.ONE;
          });
      //  AVERAGE : (number | array)*  -> number
      new Function("AVERAGE",
        MakeFunction((Fun<Value[], double>)Average));
      //  CARRAY : value * number * number -> array
      new Function("CARRAY",
        MakeFunction(CArray));
      // CHOOSE: number * any* -> any
      new Function("CHOOSE",              // Note: non-strict in arg 2...
          delegate(Sheet sheet, Expr[] es, int col, int row)
          {
            if (es.Length >= 1)
            {
              Value v0 = es[0].Eval(sheet, col, row);
              NumberValue n0 = v0 as NumberValue;
              if (n0 != null)
              {
                int index = (int)n0.value;
                if (1 <= index && index < es.Length)
                  return es[index].Eval(sheet, col, row);
                else
                  return ErrorValue.Make("Value in CHOOSE");
              }
              else if (v0 is ErrorValue)
                return v0;
              else
                return ErrorValue.Make("Value in CHOOSE");
            }
            else
              return ErrorValue.argCountErrorValue;
          });
      //  COLMAP : array * function -> array
      new Function("COLMAP",
        MakeFunction(ColMap));
      // COLS : array -> number 
      new Function("COLUMNS",
          MakeFunction(delegate(Value v0) {
            ArrayValue v0arr = v0 as ArrayValue;
            if (v0arr != null)
              return NumberValue.Make(v0arr.Cols);
            else
              return ErrorValue.argTypeErrorValue;
          }));
      //  COUNTIF : area * fExpr -> number
      new Function("COUNTIF",
        MakeFunction(CountIf));
      // HARRAY: value* -> array
      new Function("HARRAY",
        MakeFunction((Fun<Value[], Value>)HArray));
      // HCAT: value* -> array
      new Function("HCAT",
        MakeFunction((Fun<Value[], Value>)HCat));
      // IF : number any any -> any, 
      new Function("IF",                  // Note: non-strict in arg 2 and 3
          delegate(Sheet sheet, Expr[] es, int col, int row) {
            if (es.Length == 3) {
              Value v0 = es[0].Eval(sheet, col, row);
              NumberValue n0 = v0 as NumberValue;
              if (n0 != null && !Double.IsInfinity(n0.value) && !Double.IsNaN(n0.value))
                if (n0.value != 0)
                  return es[1].Eval(sheet, col, row);
                else
                  return es[2].Eval(sheet, col, row);
              else if (v0 is ErrorValue)
                return v0;
              else
                return ErrorValue.argTypeErrorValue;
            }
            else
              return ErrorValue.argCountErrorValue;
          });
      // INDEX: any* * number * number  -> any     // (row, col) indexing, offset base 1
      new Function("INDEX",
        MakeFunction(delegate(Value[] vs)
        {
          if (vs.Length != 3)
            return ErrorValue.argCountErrorValue;
          else if (vs[0] is ArrayValue)
          {
            double deltaRow = Value.ToDoubleOrNan(vs[1]),
                   deltaCol = Value.ToDoubleOrNan(vs[2]);
            return (vs[0] as ArrayValue).Index(deltaRow, deltaCol);
          }
          else
            return ErrorValue.argTypeErrorValue;
        }));
      // ISARRAY : value -> number
      new Function("ISARRAY",
          MakeFunction(delegate(Value v0)
          {
            return NumberValue.Make(IsArray(v0));
          }));
      // ISERROR : value -> number -- NOT ErrorValue-strict
      new Function("ISERROR",
        delegate(Sheet sheet, Expr[] es, int col, int row)
        {
          if (es.Length != 1)
            return ErrorValue.argCountErrorValue;
          else
            return NumberValue.Make(IsError(es[0].Eval(sheet, col, row)));
        });
      // MAP: array * function -> array
      new Function("MAP",
        MakeFunction(Map),MakeFunction(MapGPU));
      

      // MAX: value * -> number
      new Function("MAX",
        MakeFunction((Fun<Value[], double>)Max));
      // MIN: value * -> number
      new Function("MIN",
        MakeFunction((Fun<Value[], double>)Min));
      // NOT : number -> number, 
      new Function("NOT",
        MakeFunction(delegate(double n0)
        {
          return Double.IsNaN(n0) ? n0 : n0 == 0 ? 1.0 : 0.0;
        }));
      // OR : number number -> number, 
      new Function("OR",                  // Note: non-strict in args 2, 3, ...
          delegate(Sheet sheet, Expr[] es, int col, int row)
          {
            for (int i = 0; i < es.Length; i++)
            {
              Value vi = es[i].Eval(sheet, col, row);
              NumberValue ni = vi as NumberValue;
              if (ni != null)
              {
                if (ni.value != 0)
                  return NumberValue.ONE;
              }
              else if (vi is ErrorValue)
                return vi;
              else
                return ErrorValue.argTypeErrorValue;
            }
            return NumberValue.ZERO;
          });
      //  REDUCE: array * value * function -> value
      new Function("REDUCE",
        MakeFunction(Reduce));
      //  ROWMAP : array * function -> array
      new Function("ROWMAP",
        MakeFunction(RowMap));
      // ROWS : array -> number 
      new Function("ROWS",
          MakeFunction(delegate(Value v0)
        {
          ArrayValue v0arr = v0 as ArrayValue;
          if (v0arr != null)
            return NumberValue.Make(v0arr.Rows);
          else
            return ErrorValue.argTypeErrorValue;
        }));
      //  SLICE : array * number * number * number * number -> value
      new Function("SLICE",
        MakeFunction(Slice));
      new Function("MINVERSE",
        MakeFunction((Fun<Value[], Value>)Minverse));
      new Function("MMULT",
        MakeFunction((Fun<Value, Value, Value>)MMult), MakeFunction((Fun<Value, Value, Value>)MMultGPU));
      //  SUM : { number, array } * -> number
      new Function("SUM",
        MakeFunction((Fun<Value[], double>)Sum), MakeFunction((Fun<Value[], double>)SumGPU));
      //  SUMIF : area * fExpr -> number
      new Function("SUMIF",
        MakeFunction(SumIf));
      //  TABULATE : function * number * number -> array
      new Function("TABULATE",
        MakeFunction(Tabulate), MakeFunction(TabulateGPU));


      // TRANSPOSE : array -> array
      new Function("TRANSPOSE",
          MakeFunction(Transpose));
      // VARRAY: value* -> array
      new Function("VARRAY",
        MakeFunction((Fun<Value[], Value>)VArray));
      // VCAT: value* -> array
      new Function("VCAT",
        MakeFunction((Fun<Value[], Value>)VCat));
      // MAKEFUN(sdfname,outputcell,inputcells...) defines a sheet-defined function
      new Function("MAKEFUN",
      delegate(Sheet sheet, Expr[] es, int col, int row)
      {
        if (!sheet.IsFunctionSheet)
          return ErrorValue.Make("Non-function sheet");
        if (es.Length < 2)
          return ErrorValue.argCountErrorValue;
        TextConst nameConst = es[0] as TextConst;
        if (nameConst == null)
          return ErrorValue.argTypeErrorValue;
        String name = nameConst.value.value.ToUpper();
        CellRef outputCell = es[1] as CellRef;
        if (outputCell == null)
          return ErrorValue.argTypeErrorValue;
        if (outputCell.sheet != null && outputCell.sheet != sheet)
          return ErrorValue.Make("Output on another sheet");
        FullCellAddr output = outputCell.GetAbsoluteAddr(sheet, col, row);
        List<FullCellAddr> inputCells = new List<FullCellAddr>();
        for (int i = 2; i < es.Length; i++)
        {
          CellRef inputCell = es[i] as CellRef;
          if (inputCell == null)
            return ErrorValue.argTypeErrorValue;
          else if (inputCell.sheet != null && inputCell.sheet != sheet)
            return ErrorValue.Make("Input on another sheet");
          else
            inputCells.Add(inputCell.GetAbsoluteAddr(sheet, col, row));
        }
        SdfManager.CreateFunction(name, output, inputCells);
        return new TextValue(SdfManager.GetInfo(name).ToString());
      });
      // GETFUN(sdfname,arguments...) creates a FunctionValue by partial
      // application of a sheet-defined function
      new Function("GETFUN",
      delegate(Sheet sheet, Expr[] es, int col, int row)
      {
        if (es.Length < 1)
          return ErrorValue.argCountErrorValue;
        TextConst nameConst = es[0] as TextConst;
        if (nameConst == null)
          return ErrorValue.argTypeErrorValue;
        String name = nameConst.value.value;
        SdfInfo sdfInfo = SdfManager.GetInfo(name);
        if (sdfInfo == null)
          return ErrorValue.noSuchFunctionErrorValue;
        int givenCount = es.Length - 1;
        if (givenCount > sdfInfo.Arity)
          return ErrorValue.argCountErrorValue;
        Value[] arguments = new Value[givenCount];
        for (int i = 1; i < es.Length; i++)
          arguments[i-1] = es[i].Eval(sheet, col, row);
        return new FunctionValue(sdfInfo, arguments);
      });
      // APPLY(funval,arguments...) applies a FunctionValue to (possibly 
      // zero) remaining arguments
      new Function("APPLY",
        MakeFunction(delegate(Value[] vs)
      {
        if (vs.Length < 1)
          return ErrorValue.argCountErrorValue;
        FunctionValue function = vs[0] as FunctionValue;
        if (function == null)
          if (vs[0] is ErrorValue)
            return vs[0];
          else
            return ErrorValue.argTypeErrorValue;
        int argCount = vs.Length - 1;
        Value[] arguments = new Value[argCount];
        for (int i = 1; i < vs.Length; i++)
          arguments[i - 1] = vs[i];
        return function.Apply(arguments);
      }));
      // ERR("message") produces an ErrorValue with the given message
      new Function("ERR",
      delegate(Sheet sheet, Expr[] es, int col, int row)
      {
        if (es.Length != 1)
          return ErrorValue.argCountErrorValue;
        TextConst messageConst = es[0] as TextConst;
        if (messageConst == null)
          return ErrorValue.argTypeErrorValue;
        return ErrorValue.Make(messageConst.value.value);
      });
      // EXTERN("nameAndSignature", e1, ..., en) calls .NET the function "name"
      // having the given "signature", passing it converted values of e1...en 
      new Function("EXTERN",
      delegate(Sheet sheet, Expr[] es, int col, int row)
      {
        if (es.Length < 1)
          return ErrorValue.argCountErrorValue;
        TextConst nameAndSignatureConst = es[0] as TextConst;
        if (nameAndSignatureConst == null)
          return ErrorValue.argTypeErrorValue;
        try
        {
          // This retrieves the method from cache, or creates it:
          ExternalFunction ef = ExternalFunction.Make(nameAndSignatureConst.value.value);
          Value[] values = new Value[es.Length - 1];
          for (int i = 0; i < values.Length; i++)
            values[i] = es[i + 1].Eval(sheet, col, row);
          return ef.Call(values);
        }
        catch (TargetInvocationException exn)  // From external method
        {
          return ErrorValue.Make(exn.InnerException.Message);
        }
        catch (Exception exn)  // Covers a multitude of sins
        {
          return ErrorValue.Make(exn.Message);
        }
      });
      // BENCHMARK(fExpr, n) evaluates fExpr to zero-arity FunctionValue,
      // then calls it n times and returns the number of nanoseconds per call
      new Function("BENCHMARK",
        MakeFunction(delegate(Value v0, Value v1)
      {
        FunctionValue fun = v0 as FunctionValue;
        NumberValue n1 = v1 as NumberValue;
        if (fun == null || n1 == null)
          return ErrorValue.argTypeErrorValue;
        int count = (int)(n1.value);
        if (count <= 0)
          return ErrorValue.numErrorValue;
        // The following replicates some of fun.Call0(), to lift 
        // array unwrapping (although not arity checks and a cast) 
        // out of the timing loops
        if (fun.Arity != 0)
          return ErrorValue.argCountErrorValue;
        SdfInfo sdfInfo = fun.sdfInfo;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Reset();
        stopwatch.Start();
        switch (sdfInfo.Arity)
        {
          case 0:
            for (int i = count; i > 0; i--)
              sdfInfo.Call0();
            break;
          case 1:
            {
              Value arg0 = fun.args[0];
              for (int i = count; i > 0; i--)
                sdfInfo.Call1(arg0);
              break;
            }
          case 2:
            {
              Value arg0 = fun.args[0], arg1 = fun.args[1];
              for (int i = count; i > 0; i--)
                sdfInfo.Call2(arg0, arg1);
              break;
            }
          case 3:
            {
              Value arg0 = fun.args[0], arg1 = fun.args[1], arg2 = fun.args[2];
              for (int i = count; i > 0; i--)
                sdfInfo.Call3(arg0, arg1, arg2);
              break;
            }
          case 4:
            {
              Value arg0 = fun.args[0], arg1 = fun.args[1],
                arg2 = fun.args[2], arg3 = fun.args[3];
              for (int i = count; i > 0; i--)
                sdfInfo.Call4(arg0, arg1, arg2, arg3);
              break;
            }
          default:
            return ErrorValue.Make("Benchmark: Function has too many arguments");
        }
        stopwatch.Stop();
        return NumberValue.Make((1E6 * stopwatch.ElapsedMilliseconds) / count);
      }));
    }

    internal Function(String name, Applier applierCPU)
        : this(name, applierCPU, false) { }

    internal Function(String name, Applier applierCPU, Applier applierGPU)
        : this(name, applierCPU, applierGPU, false) { }

    internal Function(String name, Applier applierCPU, bool placeHolder)
        : this(name, 0, applierCPU, null, placeHolder) { }

    internal Function(String name, Applier applierCPU, Applier applierGPU, bool placeHolder)
        : this(name, 0, applierCPU, applierGPU, placeHolder) { }

      private Function(String name, int fixity, Applier applierCPU)
        : this(name, fixity, applierCPU, null, false) { }

      private Function(String name, int fixity, Applier applierCPU, Applier applierGPU, bool placeHolder)
    {
        this.name = name;
        this.applierCPU = applierCPU;
        this.applierGPU = applierGPU;
        this.fixity = fixity;
        this.isPlaceHolder = placeHolder;
        table.Add(name, this);
    }


    public static Function MakeUnknown(String name) {
      return new Function(name.ToUpper(),
                          delegate { return ErrorValue.nameErrorValue; }, null,
                          true /* placeholder */);
    }

    // Make number-valued nullary function, eg RAND()
    private static Applier MakeFunction(Fun<double> dlg) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          if (es.Length == 0)
            return NumberValue.Make(dlg());
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make number-valued constant nullary function, eg PI()
    private static Applier MakeConstant(NumberValue value) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          if (es.Length == 0)
            return value;
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make number-valued strict unary function, eg SIN(x)
    private static Applier MakeFunction(Fun<double, double> dlg) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          if (es.Length == 1)        
          {
            Value v0 = es[0].Eval(sheet, col, row);
            return NumberValue.Make(dlg(Value.ToDoubleOrNan(v0)));
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make number-valued strict binary function, eg +
    private static Applier MakeFunction(Fun<double, double, double> dlg) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          if (es.Length == 2)
          {
            Value v0 = es[0].Eval(sheet, col, row),
                  v1 = es[1].Eval(sheet, col, row);
            return NumberValue.Make(dlg(Value.ToDoubleOrNan(v0), Value.ToDoubleOrNan(v1)));
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make string-valued strict binary function, eg &
    private static Applier MakeFunction(Fun<String, String, String> dlg) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          if (es.Length == 2) {
            Value v0 = es[0].Eval(sheet, col, row),
                  v1 = es[1].Eval(sheet, col, row);
            TextValue s0 = v0 as TextValue,
                      s1 = v1 as TextValue;
            if (s0 != null && s1 != null)
              return new TextValue(dlg(s0.value, s1.value));
            else if (v0 is ErrorValue)
              return s0;
            else if (v1 is ErrorValue)
              return s1;
            else
              return ErrorValue.argTypeErrorValue;
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make boolean-valued strict binary function, eg ==
    private static Applier MakePredicate(Fun<double, double, bool> dlg) {
      return
        MakeFunction(delegate(double x, double y) {
          return Double.IsNaN(x) ? x : Double.IsNaN(y)? y : dlg(x, y) ? 1.0 : 0.0;
        });
    }

    // Make number-valued strict variadic function, eg SUM, AVERAGE
    private static Applier MakeFunction(Fun<Value[], double> dlg) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          try {
            return NumberValue.Make(dlg(Eval(es, sheet, col, row)));
          } catch (ArgumentException) {
            return ErrorValue.argTypeErrorValue;
          }
        };
    }

    // Make strict unary function, eg ROWS(A1:B7)
    private static Applier MakeFunction(Fun<Value, Value> dlg) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          if (es.Length == 1)
          {
            Value v0 = es[0].Eval(sheet, col, row);
            return v0 is ErrorValue ? v0 : dlg(v0);
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make general strict binary function, eg x&y
    private static Applier MakeFunction(Fun<Value, Value, Value> dlg)
    {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row)
        {
          if (es.Length == 2)
          {
            Value v0 = es[0].Eval(sheet, col, row),
                  v1 = es[1].Eval(sheet, col, row);
            return v0 is ErrorValue ? v0 : v1 is ErrorValue ? v1 : dlg(v0, v1);
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make general strict ternary function
    private static Applier MakeFunction(Fun<Value, Value, Value, Value> dlg)
    {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row)
        {
          if (es.Length == 3)
          {
            Value v0 = es[0].Eval(sheet, col, row),
                  v1 = es[1].Eval(sheet, col, row),
                  v2 = es[2].Eval(sheet, col, row);
            return v0 is ErrorValue ? v0 
                 : v1 is ErrorValue ? v1 
                 : v2 is ErrorValue ? v2 : dlg(v0, v1, v2);
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make general strict quaternary function
    private static Applier MakeFunction(Fun<Value, Value, Value, Value, Value> dlg)
    {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row)
        {
          if (es.Length == 4)
          {
            Value v0 = es[0].Eval(sheet, col, row),
                  v1 = es[1].Eval(sheet, col, row),
                  v2 = es[2].Eval(sheet, col, row),
                  v3 = es[3].Eval(sheet, col, row);
            return v0 is ErrorValue ? v0
                 : v1 is ErrorValue ? v1
                 : v2 is ErrorValue ? v2
                 : v3 is ErrorValue ? v3 : dlg(v0, v1, v2, v3);
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make general strict quinternary function
    private static Applier MakeFunction(Fun<Value, Value, Value, Value, Value, Value> dlg)
    {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row)
        {
          if (es.Length == 5)
          {
            Value v0 = es[0].Eval(sheet, col, row),
                  v1 = es[1].Eval(sheet, col, row),
                  v2 = es[2].Eval(sheet, col, row),
                  v3 = es[3].Eval(sheet, col, row),
                  v4 = es[4].Eval(sheet, col, row);
            return v0 is ErrorValue ? v0
                 : v1 is ErrorValue ? v1
                 : v2 is ErrorValue ? v2
                 : v3 is ErrorValue ? v3
                 : v4 is ErrorValue ? v4 : dlg(v0, v1, v2, v3, v4);
          }
          else
            return ErrorValue.argCountErrorValue;
        };
    }

    // Make general strict function, eg MMULT(A1:B3, E1:G2)
    private static Applier MakeFunction(Fun<Value[], Value> dlg) {
      return
        delegate(Sheet sheet, Expr[] es, int col, int row) {
          Value[] vs = Eval(es, sheet, col, row);
          foreach (Value v in vs)
            if (v is ErrorValue)
              return v;
          return dlg(vs);
        };
    }

    // Evaluate expression array
    private static Value[] Eval(Expr[] es, Sheet sheet, int col, int row) {
      Value[] vs = new Value[es.Length];
      for (int i = 0; i < es.Length; i++)
        vs[i] = es[i].Eval(sheet, col, row);
      return vs;
    }
  }
}
